﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;


namespace advadev.CanOpen
{

    /// <summary>
    /// Represents a emergency object write transfer
    /// </summary>
    public class EmergencyObjectWriteTransfer : ITransfer
    {

        /// <summary>for internal use</summary>
        private bool _isProducerTransfer;
        /// <summary>for internal use</summary>
        private bool _isTransferCompleteAfterSending;
        /// <summary>for internal use</summary>
        private CanOpenMessage _receivedCanOpenMessage;
        /// <summary>for internal use</summary>
        private EmergencyObjectReceivedEventHandler _emergencyObjectReceivedEventHandler;
        /// <summary>for internal use</summary>
        private Collection<CanOpenMessage> _pendingSendMessages = new Collection<CanOpenMessage>();
        /// <summary>for internal use</summary>
        private ReadOnlyCollection<CanOpenMessage> _pendingSendMessagesReference;
        /// <summary>
        /// Default producer COB-ID
        /// </summary>
        static public readonly UInt16 DefaultProducerCobId = 0x80;


        /// <summary>
        /// Initializes a new instance of the EmergencyObjectWriteTransfer class
        /// by the given parameter
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="emergencyObjectReceivedEventHandler">
        /// Emergency object received event handler (or null)
        /// </param>
        private EmergencyObjectWriteTransfer(CanOpenMessage receivedCanOpenMessage, EmergencyObjectReceivedEventHandler emergencyObjectReceivedEventHandler)
        {
            _isProducerTransfer = false;
            _receivedCanOpenMessage = receivedCanOpenMessage;
            _emergencyObjectReceivedEventHandler = emergencyObjectReceivedEventHandler;
        }

        /// <summary>
        /// Initializes a new instance of the EmergencyObjectWriteTransfer class
        /// by the given parameter
        /// </summary>
        /// <param name="emergencyErrorCode">
        /// Emergency error code to send with the emergency object
        /// </param>
        /// <param name="errorRegister">
        /// Error register to send with the emergency object
        /// </param>
        /// <param name="manufacturerSpecificErrorCode">
        /// Manufacturer specific error code to send with the emergency object
        /// </param>
        /// <param name="nodeId">
        /// Node id of the device that writes the emergency object (must be in
        /// the range of 1..127)
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Will be thrown if the emergency error code or the error register is
        /// null
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Will be thrown if the node id is not in the range of 1..127
        /// </exception>
        public EmergencyObjectWriteTransfer(CanOpenErrorCode emergencyErrorCode, CanOpenErrorRegister errorRegister, ulong manufacturerSpecificErrorCode, byte nodeId)
            : this(emergencyErrorCode, errorRegister, manufacturerSpecificErrorCode, nodeId, DefaultProducerCobId)
        {
        }

        /// <summary>
        /// Initializes a new instance of the EmergencyObjectWriteTransfer class
        /// by the given parameter
        /// </summary>
        /// <param name="emergencyErrorCode">
        /// Emergency error code to send with the emergency object
        /// </param>
        /// <param name="errorRegister">
        /// Error register to send with the emergency object
        /// </param>
        /// <param name="manufacturerSpecificErrorCode">
        /// Manufacturer specific error code to send with the emergency object
        /// </param>
        /// <param name="nodeId">
        /// Node id of the device that writes the emergency object (must be in
        /// the range of 1..127)
        /// </param>
        /// <param name="producerCobId">
        /// Producer COB-ID
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Will be thrown if the emergency error code or the error register is
        /// null
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Will be thrown if the node id is not in the range of 1..127
        /// </exception>
        public EmergencyObjectWriteTransfer(CanOpenErrorCode emergencyErrorCode, CanOpenErrorRegister errorRegister, ulong manufacturerSpecificErrorCode, byte nodeId, UInt16 producerCobId)
        {
            // check parameter
            if (emergencyErrorCode == null)
            {
                throw new ArgumentNullException("emergencyErrorCode");
            }
            if (errorRegister == null)
            {
                throw new ArgumentNullException("errorRegister");
            }
            if (!(1 <= nodeId && nodeId <= 127))
            {
                throw new ArgumentOutOfRangeException("nodeId", "The node id must be in the range of 1..127");
            }

            // initializaton
            _isProducerTransfer = true;
            NodeId = nodeId;
            ProducerCobId = producerCobId;
            EmergencyErrorCode = new CanOpenUnsigned16(0, 0, true, true) { Value = emergencyErrorCode.ErrorCode };
            ErrorRegister = new CanOpenUnsigned8(0, 0, true, true) { Value = errorRegister.ErrorRegister };
            ManufacturerSpecificErrorCode = new CanOpenUnsigned40(0, 0, true, true) { Value = manufacturerSpecificErrorCode };
        }


        /// <summary>
        /// Gets or sets the node id
        /// </summary>
        private byte NodeId
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the emergency error code
        /// </summary>
        private CanOpenUnsigned16 EmergencyErrorCode
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the error register
        /// </summary>
        private CanOpenUnsigned8 ErrorRegister
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the manufacturer specific error code
        /// </summary>
        private CanOpenUnsigned40 ManufacturerSpecificErrorCode
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the producer COB-ID
        /// </summary>
        private UInt16 ProducerCobId
        {
            get;
            set;
        }


        /// <summary>
        /// Checks if the given received can open message is a emergency object
        /// write transfer request and if so it returns as out parameter a
        /// matching emergency object write transfer instance
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="emergencyObjectWriteTransfer">
        /// A matching emergency object write transfer instance
        /// </param>
        /// <param name="emergencyObjectReceivedEventHandler">
        /// Emergency object received event handler (or null)
        /// </param>
        /// <returns>
        /// True if the received can open message is a emergency object write
        /// transfer request, false otherwise
        /// </returns>
        static public bool IsEmergencyObjectWriteTransfer(CanOpenMessage receivedCanOpenMessage, out EmergencyObjectWriteTransfer emergencyObjectWriteTransfer, EmergencyObjectReceivedEventHandler emergencyObjectReceivedEventHandler)
        {
            return IsEmergencyObjectWriteTransfer(receivedCanOpenMessage, out emergencyObjectWriteTransfer, DefaultProducerCobId, emergencyObjectReceivedEventHandler);
        }

        /// <summary>
        /// Checks if the given received can open message is a emergency object
        /// write transfer request and if so it returns as out parameter a
        /// matching emergency object write transfer instance
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <param name="emergencyObjectWriteTransfer">
        /// A matching emergency object write transfer instance
        /// </param>
        /// <param name="producerCobId">
        /// Producer COB-ID
        /// </param>
        /// <param name="emergencyObjectReceivedEventHandler">
        /// Emergency object received event handler (or null)
        /// </param>
        /// <returns>
        /// True if the received can open message is a emergency object write
        /// transfer request, false otherwise
        /// </returns>
        static public bool IsEmergencyObjectWriteTransfer(CanOpenMessage receivedCanOpenMessage, out EmergencyObjectWriteTransfer emergencyObjectWriteTransfer, UInt16 producerCobId, EmergencyObjectReceivedEventHandler emergencyObjectReceivedEventHandler)
        {
            // check parameter
            if (receivedCanOpenMessage == null)
            {
                emergencyObjectWriteTransfer = null;
                return false;
            }

            // check node id
            UInt16 nodeId = receivedCanOpenMessage.CanId;
            nodeId -= producerCobId;
            if (!(1 <= nodeId && nodeId <= 127))
            {
                emergencyObjectWriteTransfer = null;
                return false;
            }

            // create transfer instance
            emergencyObjectWriteTransfer = new EmergencyObjectWriteTransfer(receivedCanOpenMessage, emergencyObjectReceivedEventHandler);
            return true;
        }

        /// <summary>
        /// Starts the producer transfer
        /// </summary>
        private void startProducerTransfer()
        {
            UInt16 canId = ProducerCobId;
            canId += NodeId;
            List<byte> byteArray = new List<byte>();
            byteArray.AddRange((EmergencyErrorCode as ICanOpenValueObject).ToByteArray());
            byteArray.AddRange((ErrorRegister as ICanOpenValueObject).ToByteArray());
            if (ManufacturerSpecificErrorCode != null)
            {
                byteArray.AddRange((ManufacturerSpecificErrorCode as ICanOpenValueObject).ToByteArray());
            }
            byte byte0 = (byteArray.Count > 0 ? byteArray[0] : (byte)0);
            byte byte1 = (byteArray.Count > 1 ? byteArray[1] : (byte)0);
            byte byte2 = (byteArray.Count > 2 ? byteArray[2] : (byte)0);
            byte byte3 = (byteArray.Count > 3 ? byteArray[3] : (byte)0);
            byte byte4 = (byteArray.Count > 4 ? byteArray[4] : (byte)0);
            byte byte5 = (byteArray.Count > 5 ? byteArray[5] : (byte)0);
            byte byte6 = (byteArray.Count > 6 ? byteArray[6] : (byte)0);
            byte byte7 = (byteArray.Count > 7 ? byteArray[7] : (byte)0);
            _pendingSendMessages.Add(new CanOpenMessage(canId, byte0, byte1, byte2, byte3, byte4, byte5, byte6, byte7, 8));
            _isTransferCompleteAfterSending = true;
        }

        /// <summary>
        /// Starts the consumer transfer
        /// </summary>
        private void startConsumerTransfer()
        {
            // process data
            if (_emergencyObjectReceivedEventHandler != null)
            {
                CanOpenUnsigned16 emergencyErrorCode = new CanOpenUnsigned16(0, 0, true, true);
                List<byte> byteArray = new List<byte>() { _receivedCanOpenMessage.Byte0, _receivedCanOpenMessage.Byte1 };
                (emergencyErrorCode as ICanOpenValueObject).FromByteArray(byteArray);
                CanOpenUnsigned8 errorRegister = new CanOpenUnsigned8(0, 0, true, true);
                byteArray = new List<byte>() { _receivedCanOpenMessage.Byte2 };
                (errorRegister as ICanOpenValueObject).FromByteArray(byteArray);
                CanOpenUnsigned40 manufacturerSpecificErrorCode = new CanOpenUnsigned40(0, 0, true, true);
                byteArray = new List<byte>() { _receivedCanOpenMessage.Byte3, _receivedCanOpenMessage.Byte4, _receivedCanOpenMessage.Byte5, _receivedCanOpenMessage.Byte6, _receivedCanOpenMessage.Byte7 };
                (manufacturerSpecificErrorCode as ICanOpenValueObject).FromByteArray(byteArray);
                _emergencyObjectReceivedEventHandler(this, new EmergencyObjectReceivedEventArgs(new CanOpenErrorCode((UInt16)emergencyErrorCode.Value), new CanOpenErrorRegister((byte)errorRegister.Value), manufacturerSpecificErrorCode.Value));
            }

            // end transfer
            TransferComplete = true;
        }


        #region ITransfer implementation
        /// <summary>
        /// Gets a flag indicating whether the transfer is complete (after
        /// sending the pending send messages)
        /// </summary>
        public bool TransferComplete
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the abort code (null for successful transfers)
        /// </summary>
        public CanOpenAbortCode AbortCode
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the pending send messages
        /// </summary>
        public ReadOnlyCollection<CanOpenMessage> PendingSendMessages
        {
            get
            {
                if (_pendingSendMessagesReference == null)
                {
                    _pendingSendMessagesReference = new ReadOnlyCollection<CanOpenMessage>(_pendingSendMessages);
                }
                return _pendingSendMessagesReference;
            }
        }


        /// <summary>
        /// Starts the transfer
        /// </summary>
        public void Start()
        {
            if (_isProducerTransfer)
            {
                startProducerTransfer();
            }
            else
            {
                startConsumerTransfer();
            }
        }

        /// <summary>
        /// Times out the transfer
        /// </summary>
        public void TimeOut()
        {
            AbortCode = new CanOpenAbortCode(0x00, 0x00, 0x04, 0x05);
        }

        /// <summary>
        /// Finalizes the transfer
        /// </summary>
        public void FinalizeTransfer()
        {
        }

        /// <summary>
        /// Continues the transfer after sending messages
        /// </summary>
        public void ContinueAfterSendingMessages()
        {
            _pendingSendMessages.Clear();
            if (_isTransferCompleteAfterSending)
            {
                TransferComplete = true;
            }
        }

        /// <summary>
        /// Processes the received message
        /// </summary>
        /// <param name="receivedCanOpenMessage">
        /// Received can open message
        /// </param>
        /// <returns>
        /// True, if the given can open message was related to this transfer,
        /// false otherwise
        /// </returns>
        public bool ProcessReceivedMessage(CanOpenMessage receivedCanOpenMessage)
        {
            return false;
        }
        #endregion ITransfer implementation

    }

}
